package luj.tool.jnproxyan.proxy.util.data.load.internal;

import luj.data.api.Data;
import luj.data.api.field.TypeField;
import luj.data.api.field.TypeInField;
import luj.tool.jnproxyan.proxy.database.DatabaseState;
import luj.tool.jnproxyan.proxy.util.data.DataDirtySaver;
import org.jooq.DSLContext;
import org.jooq.Field;
import org.jooq.Record;
import org.jooq.Table;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Map;

@Component
public class DataDeepLoader {

  public DataDeepLoader inject(Class<?> type, Long dataId, DatabaseState dbState, Map<String, Data> loadCache) {
    DataDeepLoader result = new DataDeepLoader();
    result._type = type;
    result._dataId = dataId;
    result._loadCache = loadCache;
    result._dbState = dbState;
    result._jooq = dbState.getJooq();

    result._keyMaker = _keyMaker;
    result._dataMaker = _dataMaker;

    return result;
  }

  public void load() {
    loadImpl(_type, _dataId);
  }

  private void loadImpl(Class<?> type, Long id) {
    String cacheKey = _keyMaker.make(type, id);
    Data data = _loadCache.computeIfAbsent(cacheKey, k -> loadFromDb(type, id));

    data.getType().getFields().stream()
        .filter(f -> f.getType().isMap())
        .forEach(f -> loadMapDeep(f, data));
  }

  private void loadMapDeep(TypeField field, Data data) {
    TypeInField.Map typeAsMap = field.getType().asMapType();
    TypeInField valueType = typeAsMap.getValueType();
    if (valueType.isPrimitive()) {
      return;
    }

    Map<?, Number> idMap = field.getValue(data);
    Class<?> valueClass = valueType.asJavaClass();

    for (Number dataId : idMap.values()) {
      loadImpl(valueClass, dataId.longValue());
    }
  }

  private Data loadFromDb(Class<?> type, Long id) {
    String valueStr = _jooq.select(F_DATA)
        .from(T_DATA)
        .where(F_TABLE.eq(type.getName())).and(F_ID.eq(id))
        .fetchOne(F_DATA);

    return _dataMaker.inject(id, valueStr, _dbState, type).make();
  }

  private static final Table<Record> T_DATA = DataDirtySaver.T_DATA;
  private static final Table<Record> T_DATAN = DataDirtySaver.T_DATAN;

  private static final Field<Long> F_ID = DataDirtySaver.F_ID;
  private static final Field<String> F_TABLE = DataDirtySaver.F_TABLE;
  private static final Field<String> F_DATA = DataDirtySaver.F_DATA;
  private static final Field<Long> F_PARENT = DataDirtySaver.F_PARENT;

  Class<?> _type;
  Long _dataId;

  DatabaseState _dbState;
  DSLContext _jooq;

  Map<String, Data> _loadCache;

  @Autowired
  CacheKeyMaker _keyMaker;

  @Autowired
  DataInLoadMaker _dataMaker;
}
